package online.struts.chain.processor;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import online.filter.FilterUtil;
import online.model.UniModel;
import online.model.session.ActionParameter;
import online.model.session.SessionAttribute;
import online.struts.action.UniForm;
import online.struts.mapping.RequestMapping;

import org.apache.commons.fileupload.FileUploadException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts.Globals;
import org.apache.struts.action.ActionForm;
import org.apache.struts.chain.commands.servlet.PopulateActionForm;
import org.apache.struts.chain.contexts.ActionContext;
import org.apache.struts.chain.contexts.ServletActionContext;
import org.apache.struts.config.ActionConfig;
import org.apache.struts.config.ModuleConfig;
import org.apache.struts.upload.FormFile;
import org.apache.struts.upload.MultipartRequestHandler;
import org.apache.struts.util.ModuleUtils;

import core.config.Factory;
import core.exception.PhysicalException;

/**
 * データ設定
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class PopulateProcessor extends PopulateActionForm {
	/** ログ出力用クラス */
	private static final Logger LOGGER = LogManager.getLogger(PopulateProcessor.class);

	/** エンコード */
	private String encoding;

	/**
	 * @return the encoding
	 */
	public String getEncoding() {
		return this.encoding;
	}

	/**
	 * @param val the encoding to set
	 */
	public void setEncoding(final String val) {
		this.encoding = val;
	}

	/**
	 * @see org.apache.struts.chain.commands.servlet.PopulateActionForm
	 * #populate(org.apache.struts.chain.contexts.ActionContext,
	 * org.apache.struts.config.ActionConfig, org.apache.struts.action.ActionForm)
	 */
	@Override
	protected void populate(final ActionContext<String, Object> context,
					final ActionConfig actionConfig, final ActionForm actionForm) {

		HttpServletRequest request = ServletActionContext.class.cast(context).getRequest();

		debugPrint(actionConfig, request);

		if (UniForm.class.isInstance(actionForm) && RequestMapping.class.isInstance(actionConfig)) {
			UniForm uf = UniForm.class.cast(actionForm);
			RequestMapping rm = RequestMapping.class.cast(actionConfig);

			MultipartRequestHandler handler = getMultipartHandler(request);
			if (handler != null && request.getAttribute(Globals.EXCEPTION_KEY) == null) {
				handleRequest(rm, uf, handler, request);
			}
			uf.setMultipartRequestHandler(handler);

			UniModel um = ActionParameter.getUniModelToSet(uf.getActionParameter());

			uf.setActionParameter(new ActionParameter(request, rm.getGid(),
							Factory.cast(rm.getProperties()),
							createParameterMap(uf, request), um));
			uf.populate(um);
			uf.setSessionUser(SessionAttribute.getSessionUser(request));
			uf.setSessionAttribute(new SessionAttribute(request));

			uf.getActionParameter().setToken();
			request.setAttribute(ActionParameter.TAG_TOKEN, uf.getActionParameter().getToken());

			context.setFormValid(Boolean.TRUE);
		}
	}

	/**
	 * デバッグ印刷処理
	 * @param actionConfig ActionConfig
	 * @param request HttpServletRequest
	 */
	private void debugPrint(final ActionConfig actionConfig, final HttpServletRequest request) {
		if (LOGGER.isDebugEnabled()) {
			for (final String name : Collections.list(request.getHeaderNames())) {
				LOGGER.debug(name + ":" + request.getHeader(name));
			}
			LOGGER.debug("mapping:" + actionConfig.getPath());
			LOGGER.debug("PathTranslated:" + request.getPathTranslated());

			LOGGER.debug("ContextPath:" + request.getContextPath());
			LOGGER.debug("PathInfo:" + request.getPathInfo());
			LOGGER.debug("QueryString:" + request.getQueryString());
			LOGGER.debug("RequestURI:" + request.getRequestURI());
			LOGGER.debug("RequestURL:" + request.getRequestURL());
			LOGGER.debug("ServletPath:" + request.getServletPath());
			LOGGER.debug("IncludeContextPath:"
							+ request.getAttribute("javax.servlet.include.context_info"));
			LOGGER.debug("IncludePathInfo:"
							+ request.getAttribute("javax.servlet.include.path_info"));
			LOGGER.debug("IncludeQueryString:"
							+ request.getAttribute("javax.servlet.include.query_string"));
			LOGGER.debug("IncludeRequestURI:"
							+ request.getAttribute("javax.servlet.include.request_uri"));
			LOGGER.debug("IncludeServletPath:"
							+ request.getAttribute("javax.servlet.include.servlet_path"));
		}
	}

	/**
	 * マルチパートハンドラ取得
	 * @param request リクエスト
	 * @return マルチパートハンドラ
	 */
	private MultipartRequestHandler getMultipartHandler(final HttpServletRequest request) {
		String content = request.getContentType();
		if (content == null || !content.startsWith("multipart/form-data")
						|| FilterUtil.isGetMethod(request.getMethod())) {
			return null;
		}

		ModuleConfig moduleConfig = ModuleUtils.getInstance().getModuleConfig(request);

		String multipartClass = moduleConfig.getControllerConfig().getMultipartClass();
		if (multipartClass == null) {
			return null;
		}

		return Factory.create(
				Factory.loadClass(multipartClass).asSubclass(MultipartRequestHandler.class));
	}

	/**
	 * リクエストハンドル処理
	 * @param rm RequestMapping
	 * @param uf UniForm
	 * @param handler ハンドラ
	 * @param request リクエスト
	 */
	private void handleRequest(final RequestMapping rm, final UniForm uf,
					final MultipartRequestHandler handler, final HttpServletRequest request) {
		try {
			uf.getServletWrapper().setServletFor(handler);
			handler.setMapping(rm);
			handler.handleRequest(request);

		} catch (final ServletException ex) {
			if (FileUploadException.class.isInstance(ex.getCause())) {
				LogManager.getLogger().info(ex.getCause().getMessage());
				throw new PhysicalException(ex);
			}
			throw new RuntimeException(ex);
		}
	}

	/**
	 * パラメタマップ作成
	 *
	 * @param uf 汎用モデル
	 * @param request リクエスト
	 * @return パラメタマップ
	 */
	private Map<String, String[]> createParameterMap(final UniForm uf,
					final HttpServletRequest request) {
		Map<String, String[]> map = new HashMap<>();
		map.putAll(toFileElements(uf.getMultipartRequestHandler()));
		map.putAll(request.getParameterMap());
		map.putAll(FilterUtil.toParameterMap(
						FilterUtil.getQueryString(request), getEncoding(request)));
		return map;
	}

	/**
	 * ファイル名配列化
	 * @param mrh MultipartRequestHandler
	 * @return FileElementsマップ
	 */
	private Map<String, String[]> toFileElements(final MultipartRequestHandler mrh) {
		if (mrh != null && mrh.getFileElements() != null) {
			return mrh.getFileElements().entrySet().stream().collect(Collectors.toMap(
						e -> e.getKey(),
						e -> e.getValue().stream().map(FormFile::getFileName).toArray(String[]::new)
					)
			);
		}
		return Collections.emptyMap();
	}

	/**
	 * エンコーディング取得
	 * @param request リクエスト
	 * @return エンコーディング
	 */
	private String getEncoding(final HttpServletRequest request) {
		if (!Objects.toString(this.encoding, "").isEmpty()) {
			return this.encoding;
		}
		return request.getCharacterEncoding();
	}
}
